using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace XTYAPI.通讯1
{
    /// <summary>
    /// ModbusTCP客户端 QQ851879790 QQ技术支持群1107658466
    /// </summary>
    public class ModbusTCP
    {
        Socket ModbusTcpClient;//客户端
        ushort ID = 0;//通讯ID
        bool connect;
        string ip;
        int port;
        /// <summary>
        /// 接收超时周期
        /// </summary>
        public int MaxDelayCycle { get; set; } = 10;
        object lockModbus = new object();
        /// <summary>
        /// 重连
        /// </summary>
        /// <returns></returns>
        public bool RestConnect()
        {
            return Connect(ip, port);
        }
        /// <summary>
        /// 连接ModbusServer
        /// </summary>
        /// <param name="ip"></param>
        /// <param name="port"></param>
        /// <returns></returns>
        public bool Connect(string ip, int port = 502)
        {
            if (this.ip != ip) this.ip = ip;
            if (this.port != port) this.port = port;
            DisConnect();
            ModbusTcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建sokcet tcp连接对象
            IAsyncResult asyncResult = ModbusTcpClient.BeginConnect(new IPEndPoint(IPAddress.Parse(ip), port), null, null);//异步连接
            asyncResult.AsyncWaitHandle.WaitOne(3000, true);//阻塞最多等待3秒
            if (!asyncResult.IsCompleted)//异步操作没完成 就关掉tcp连接 返回失败
            {
                ModbusTcpClient.Close();
                connect = false;
                return false;
            }
            connect = true;
            return true;
        }
        /// <summary>
        /// 断开连接
        /// </summary>
        public void DisConnect()
        {
            if (ModbusTcpClient != null)
            {
                ModbusTcpClient.Close();
            }
        }
        /// <summary>
        /// 连接状态
        /// </summary>
        public bool IsConnected { get { return connect; } }
        /// <summary>
        /// 错误码
        /// </summary>
        public byte ErrCode { get; private set; }
        /// <summary>
        /// 错误字符串
        /// </summary>
        public string ErrStr { get;private set; }
        // MODBUS读保持寄存器 
        //主站请求：00 00 00 00 00 06  00 03 00 00 00 01
        //地址    1字节（无意义,因为是TCP协议，在串口服务器里有意义）
        //功能码  1字节   0x03 批量读数据
        //起始寄存器地址  2字节   
        //寄存器数量  2字节   
        /// <summary>
        /// 03功能码 读保持寄存器 4区 40001-49999
        /// </summary>
        /// <param name="address"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public byte[] Read4X(uint address, int count)
        {
            if (count > 125)
            {
                GetSendErr(254);
                return null;
            }
            byte[] send = new byte[12];
            //ModbusTCP报文封装
            ID++;
            byte[] id = BitConverter.GetBytes(ID);
            send[0] = id[1]; //此次通信事务处理标识符2个字节
            send[1] = id[0];

            send[2] = 00;//==00该指令遵循modbusTcp; 2个字节
            send[3] = 00;

            send[4] = 00;//后面数据字节长度
            send[5] = 06;

            send[6] = 00;//站地址无
            send[7] = 03;//功能码

            send[8] = (byte)(address >>8);//寄存器地址 2个字节
            send[9] = (byte)(address & 0xff);


            send[10] = (byte)(count >>8);//寄存器个数 2个字节
            send[11] = (byte)(count & 0xff);
            byte[] resByte = SendRecive(send);
            if (resByte?.Length == 9 + count * 2 && resByte[0] == send[0] && resByte[1] == send[1] && resByte[7] == send[7] && resByte[8] == count * 2)
            {
                byte[] userByte = new byte[count * 2];
                for (int i = 0; i < userByte.Length; i++)
                {
                    userByte[i] = resByte[9 + i];
                }
                return userByte;
            }
            GetReciveErr(send, resByte);
            return null;
        }
        /// <summary>
        /// 16功能码 写保持寄存器 4区 40001-49999
        /// </summary>
        /// <param name="address">字地址</param>
        /// <param name="array">要写入的字节数组2的倍数</param>
        /// <returns></returns>
        public bool Write4X(int address, byte[] array)
        {
            byte byteCount = (byte)array.Length;
            short wordCount = (short)(array.Length / 2);
            if (wordCount > 123)
            {
                GetSendErr(253);
                return false;
            }        
            int Conut = 7 + byteCount;
            byte[] send = new byte[13 + byteCount];
            //ModbusTCP报文封装
            ID++;
            byte[] id = BitConverter.GetBytes(ID);
            send[0] = id[1]; //此次通信事务处理标识符2个字节
            send[1] = id[0];

            send[2] = 00;//==00该指令遵循modbusTcp; 2个字节
            send[3] = 00;

            send[4] = 0;//后面数据字节长度
            send[5] = (byte)Conut;

            send[6] = 0;//站地址无
            send[7] = 0x10;//功能码

            send[8] = (byte)(address >>8 );//起始寄存器地址 2个字节
            send[9] = (byte)(address & 0xff);

            send[10] =(byte)(wordCount>>8);//写寄存器数量 2个字节
            send[11] =(byte)(wordCount&0xff);

            send[12] = byteCount;//写字节数 *2
            for (int i = 0; i < byteCount; i++)//数据内容
            {
                send[i + 13] = array[i];
            }
            byte[] ResByte = SendRecive(send);
            if (ResByte?.Length > 11 && ResByte[0] == send[0] && ResByte[1] == send[1] && ResByte[7] == send[7] && ResByte[11] == array.Length / 2)//报文正确判定
            {
                return true;
            }
            GetReciveErr(send, ResByte);
            return false;
        }
        /// <summary>
        /// 01功能码 读输出继电器 0区 1-9999
        /// </summary>
        /// <param name="address">起始地址</param>
        /// <param name="count">开关量数量</param>
        /// <returns></returns>
        public bool[] Read0X(uint address, int count)
        {
            if (count > 2000)
            {
                GetSendErr(252);
                return null;
            }
            byte[] send = new byte[12];
            //ModbusTCP报文封装
            ID++;
            byte[] id = BitConverter.GetBytes(ID);
            send[0] = id[1]; //此次通信事务处理标识符2个字节
            send[1] = id[0];

            send[2] = 00;//==00该指令遵循modbusTcp; 2个字节
            send[3] = 00;

            send[4] = 00;//后面数据字节长度
            send[5] = 06;

            send[6] = 00;//站地址无
            send[7] = 01;//功能码

            send[8] = (byte)(address >>8);//寄存器地址 2个字节
            send[9] = (byte)(address & 0xff);

            send[10] = (byte)(count >>8);//寄存器个数 2个字节
            send[11] = (byte)(count & 0xff);
            byte[] resByte = SendRecive(send);
            int reciveCount = count / 8 + (count % 8 > 0 ? 1 : 0);//计算应该回复的字节数
            if (resByte?.Length == 9 + reciveCount && resByte[0] == send[0] && resByte[1] == send[1] && resByte[7] == send[7] && resByte[8] == reciveCount)
            {
                bool[] ResBool = new bool[count];
                for (int i = 0; i < ResBool.Length; i++)
                {
                    byte r1 = resByte[9 + i / 8];//获取对应的字节
                    byte r2 = (byte)(i % 8);//获取对应的位
                    ResBool[i] = (r1 & (1 << r2)) > 0; //字节与对应2进制位位权是否大于0
                }
                return ResBool;
            }
            GetReciveErr(send, resByte);
            return null;
        }

        /// <summary>
        /// 15功能码 写输出继电器 0区 1-9999
        /// </summary>
        /// <param name="address"></param>
        /// <param name="array"></param>
        /// <returns></returns>
        public bool Write0X(int address, bool[] array)
        {
            if (array.Length>1968)
            {
                GetSendErr(251);
                return false;
            }
            byte byteCount = (byte)(array.Length / 8 + (array.Length % 8 > 0 ? 1 : 0));//计算要发送的字节数
            int Conut = 7 + byteCount;
            byte[] send = new byte[13 + byteCount];
            //ModbusTCP报文封装
            ID++;
            byte[] id = BitConverter.GetBytes(ID);
            send[0] = id[1]; //此次通信事务处理标识符2个字节
            send[1] = id[0];

            send[2] = 00;//==00该指令遵循modbusTcp; 2个字节
            send[3] = 00;

            send[4] = 0;//后面数据字节长度
            send[5] = (byte)Conut;

            send[6] = 0;//站地址无
            send[7] = 0x0f;//功能码

            send[8] = (byte)(address >> 8);//起始寄存器地址 2个字节
            send[9] = (byte)(address & 0xff);

            send[10] = (byte)(array.Length >> 8);//写输出继电器数量 2个字节
            send[11] = (byte)(array.Length & 0xff);

            send[12] = byteCount;//写字节数 
            for (int i = 0, k = 0, h = 0; i < array.Length; i++,h++)//数据内容
            {
                if (h >= 8) { h = 0; k++; }//位和字节偏移处理   
                byte byte1 = send[k + 13];//取当前数据
                byte byte2 = (byte)(1 << h);//获取位权
                byte1 = array[i] ? (byte)(byte1 | byte2) : (byte)(byte1 & ~byte2);//true或  false与               
                send[k + 13] = byte1;//值类型数据还回去   
            }
            byte[] ResByte = SendRecive(send);//发送
            if (ResByte?.Length > 11 && //为空判断
            ResByte[0] == send[0] && //标识符高8位
            ResByte[1] == send[1] && //标识符低8位
            ResByte[7] == send[7] && //功能码
            ResByte[10] == send[10] && //已写线圈高8位
            ResByte[11] == send[11])//已写线圈低8位
            {
                return true;//数据正确
            }
            GetReciveErr(send, ResByte);
            return false;
        }
        /// <summary>
        /// 02功能码 读输入继电器 1区 10001-19999
        /// </summary>
        /// <param name="address">起始地址</param>
        /// <param name="count">开关量数量</param>
        /// <returns></returns>
        public bool[] Read1X(uint address, int count)
        {
            if (count > 2000)
            {
                GetSendErr(250);
                return null;
            }
            byte[] send = new byte[12];
            //ModbusTCP报文封装
            ID++;
            byte[] id = BitConverter.GetBytes(ID);
            send[0] = id[1]; //此次通信事务处理标识符2个字节
            send[1] = id[0];

            send[2] = 00;//==00该指令遵循modbusTcp; 2个字节
            send[3] = 00;

            send[4] = 00;//后面数据字节长度
            send[5] = 06;

            send[6] = 00;//站地址无
            send[7] = 02;//功能码

            send[8] = (byte)(address >> 8);//寄存器地址 2个字节
            send[9] = (byte)(address & 0xff);

            send[10] = (byte)(count>>8);//寄存器个数 2个字节
            send[11] = (byte)(count % 0xff);
            byte[] resByte = SendRecive(send);
            int reciveCount = count / 8 + (count % 8 > 0 ? 1 : 0);//计算应该回复的字节数
            if (resByte?.Length == 9 + reciveCount && 
                resByte[0] == send[0] && resByte[1] == send[1] 
                && resByte[7] == send[7] && 
                resByte[8] == reciveCount)
            {
                bool[] ResBool = new bool[count];
                for (int i = 0; i < ResBool.Length; i++)
                {
                    byte r1 = resByte[9 + i / 8];//获取对应的字节
                    byte r2 = (byte)(i % 8);//获取对应的位
                    ResBool[i] = (r1 & (1 << r2)) > 0; //字节与对应2进制位位权是否大于0
                }
                return ResBool;
            }
            GetReciveErr(send, resByte);
            return null;
        }

        /// <summary>
        /// 04功能码 读输入字 1区 30001-39999
        /// </summary>
        /// <param name="address"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public byte[] Read3X(uint address, int count)
        {
            if (count > 125)
            {
                GetSendErr(249);
                return null;
            }
            byte[] send = new byte[12];
            //ModbusTCP报文封装
            ID++;
            byte[] id = BitConverter.GetBytes(ID);
            send[0] = id[1]; //此次通信事务处理标识符2个字节
            send[1] = id[0];

            send[2] = 00;//==00该指令遵循modbusTcp; 2个字节
            send[3] = 00;

            send[4] = 00;//后面数据字节长度
            send[5] = 06;

            send[6] = 00;//站地址无
            send[7] = 04;//功能码

            send[8] = (byte)(address>>8);//寄存器地址 2个字节
            send[9] = (byte)(address &0xff);


            send[10] = (byte)(count>>8);//寄存器个数 2个字节
            send[11] = (byte)(count &0xff);
            byte[] resByte = SendRecive(send);
            if (resByte?.Length == 9 + count * 2 && resByte[0] == send[0] && resByte[1] == send[1] && resByte[7] == send[7] && resByte[8] == count * 2)
            {
                byte[] userByte = new byte[count * 2];
                for (int i = 0; i < userByte.Length; i++)
                {
                    userByte[i] = resByte[9 + i];
                }
                return userByte;
            }
            GetReciveErr(send, resByte);
            return null;
        }
        /// <summary>
        /// 获取发送错误报文 自定义
        /// </summary>
        private void GetSendErr(int err)
        {
            switch (err)
            {
                case 254:
                    ErrStr = "读寄存器数量不能超过125";
                    break;
                case 253:
                    ErrStr = "写寄存器数量不能超过123";
                    break;
                case 252:
                    ErrStr = "读输出继电器数量不能超过2000";
                    break;
                case 251:
                    ErrStr = "写输出继电器数量不能超过1968个";
                    break;
                case 250:
                    ErrStr = "读输入继电器数量不能超过2000";
                    break;
                case 249:
                    ErrStr = "读输入寄存器数量不能超过125";
                    break;
                default:
                    break;
            }
        }
        /// <summary>
        /// 获取报文错误码
        /// </summary>
        /// <param name="send"></param>
        /// <param name="recive"></param>
        private void GetReciveErr(byte[] send, byte[] recive)
        {
            if (recive == null)
            {
                ErrCode = 255;
                ErrStr = "报文为空";
                return;
            }
            if (recive?.Length > 8 && //为空判断
                recive[0] == send[0] && //标识符高8位
                recive[1] == send[1] && //标识符低8位
                recive[7] == send[7] + 0x80//有错误码
                )
            {
                ErrCode = recive[8];//获取错误码
                switch (ErrCode)
                {
                    case 1:
                        ErrStr = "未知错误";
                        break;
                    case 2:
                        ErrStr = "非法数据地址";
                        break;
                    case 3:
                        ErrStr = "非法数据值";
                        break;
                    case 4:
                        ErrStr = "从站设备故障";
                        break;
                    case 5:
                        ErrStr = "确认";
                        break;
                    case 7:
                        ErrStr = "从属设备忙";
                        break;
                    case 8:
                        ErrStr = "存储奇偶性差错";
                        break;
                    case 0xa:
                        ErrStr = "不可用网关路径";
                        break;
                    case 0xb:
                        ErrStr = "网关目标设备响应失败";
                        break;
                    default:
                        ErrStr = "未知错误";
                        break;
                }
            }
            else
            {
                ErrCode = 0;
                ErrStr = string.Empty;
            }
        }

        /// <summary>
        /// 发送并接收报文
        /// </summary>
        /// <param name="arry"></param>
        /// <returns></returns>
        public byte[] SendRecive(byte[] arry)
        {
            int delay = 0;
            try
            {
                Monitor.Enter(lockModbus);
                int reslut = ModbusTcpClient.Send(arry);
                while (ModbusTcpClient.Available == 0)
                {
                    Thread.Sleep(10);
                    delay++;
                    if (delay > MaxDelayCycle)
                    {
                        break;
                    }
                }
                byte[] resByte = new byte[ModbusTcpClient.Available];
                reslut = ModbusTcpClient.Receive(resByte);
                return resByte;
            }
            catch (Exception ex)
            {
                connect = false;
                return null;
            }
            finally
            {
                Monitor.Exit(lockModbus);
            }
        }
    }
}
